package com.xinke.module.system.handler.auth;

import com.google.common.annotations.VisibleForTesting;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import com.xinke.component.common.enums.CommonStatusEnum;
import com.xinke.component.common.enums.UserTypeEnum;
import com.xinke.component.common.util.monitor.TracerUtils;
import com.xinke.component.common.util.servlet.ServletUtils;
import com.xinke.component.common.util.validation.ValidationUtils;
import com.xinke.module.system.api.logger.dto.LoginLogCreateReqDTO;
import com.xinke.module.system.convert.auth.AuthConvert;
import com.xinke.module.system.enums.logger.LoginLogTypeEnum;
import com.xinke.module.system.enums.logger.LoginResultEnum;
import com.xinke.module.system.enums.oauth2.OAuth2ClientConstants;
import com.xinke.module.system.manager.oauth2.OAuth2Manager;
import com.xinke.module.system.model.entity.oauth2.OAuth2AccessTokenDO;
import com.xinke.module.system.model.entity.user.AdminUserDO;
import com.xinke.module.system.model.vo.auth.AuthLoginReqVO;
import com.xinke.module.system.model.vo.auth.AuthLoginRespVO;
import com.xinke.module.system.model.vo.auth.CaptchaVerificationReqVO;
import com.xinke.module.system.service.logger.LoginLogService;
import com.xinke.module.system.service.user.AdminUserService;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.Objects;

import static com.xinke.component.common.exception.util.ServiceExceptionUtil.exception;
import static com.xinke.module.system.enums.ErrorCodeConstants.*;

/**
 * @Author yzx
 * @CreateTime 2025/3/20 23:40
 * @Version 1.0
 * @Description: 认证核心管理实现
 */
@Component
public class AuthHandler {

    @Resource
    private OAuth2Manager auth2Manager;
    @Resource
    private AdminUserService userService;
    @Resource
    private LoginLogService loginLogService;

    @Resource
    private Validator validator;
    @Resource
    private CaptchaService captchaService;
    /**
     * 验证码的开关，默认为 true
     */
    @Value("${xinke.captcha.enable:true}")
    @Setter // 为了单测：开启或者关闭验证码
    private Boolean captchaEnable;

    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
        // 1、验证码验证
        validateCaptcha(reqVO);

        // 2、用户名和密码验证
        // 使用账号密码，进行登录
        AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());
        // 3、创建 token
        // 如果 socialType 非空，说明需要绑定社交用户
        if (reqVO.getSocialType() != null) {
           
        }
        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getUserId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
    }

    /**
     *  创建 Token 令牌，记录登录日志
     * @param userId
     * @param userName
     * @param logType
     * @return
     */
    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String userName, LoginLogTypeEnum logType) {
        // 插入登陆日志
        createLoginLog(userId, userName, logType, LoginResultEnum.SUCCESS);
        // 创建访问令牌
        OAuth2AccessTokenDO accessTokenDO = auth2Manager.createAccessToken(userId, getUserType().getValue(),
                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
        // 构建返回结果
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    public AdminUserDO authenticate(String userName, String password) {
        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;
        // 校验账号是否存在
        AdminUserDO user = userService.getUserByUsername(userName);
        if (user == null) {
            createLoginLog(null, userName, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        if (!userService.isPasswordMatch(password, user.getUserPassword())) {
            createLoginLog(user.getUserId(), userName, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        // 校验是否禁用
        if (CommonStatusEnum.isDisable(user.getUserStatus())) {
            createLoginLog(user.getUserId(), userName, logTypeEnum, LoginResultEnum.USER_DISABLED);
            throw exception(AUTH_LOGIN_USER_DISABLED);
        }
        return user;
    }

    /**
     * 验证码校验
     *
     * @param reqVO
     * @VisibleForTesting 测试的时候可以将私有方法或默认方法转为公有
     */
    @VisibleForTesting
    private void validateCaptcha(AuthLoginReqVO reqVO) {
        ResponseModel response = doValidateCaptcha(reqVO);
        // 校验验证码
        if (!response.isSuccess()) {
            // 创建登录失败日志（验证码不正确)
            createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);
            throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());
        }
    }

    /**
     *  创建登录日志
     * @param userId 用户id
     * @param userName 用户名
     * @param logTypeEnum 用户类型
     * @param loginResult 登录结果
     */
    private void createLoginLog(Long userId, String userName,LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
        // 插入登录日志
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(logTypeEnum.getType());
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(userId);
        reqDTO.setUserType(getUserType().getValue());
        reqDTO.setUserName(userName);
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setLoginResult(loginResult.getResult());
        loginLogService.createLoginLog(reqDTO);
        // 更新最后登录时间
        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
            userService.updateUserLogin(userId, ServletUtils.getClientIP());
        }
    }

    private UserTypeEnum getUserType() {
        return UserTypeEnum.ADMIN;
    }

    private ResponseModel doValidateCaptcha(CaptchaVerificationReqVO reqVO) {
        // 如果验证码关闭，则不进行校验
        if (!captchaEnable) {
            return ResponseModel.success();
        }
        ValidationUtils.validate(validator, reqVO, CaptchaVerificationReqVO.CodeEnableGroup.class);
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
        return captchaService.verification(captchaVO);
    }
}
